<?php

// +----------------------------------------------------------------------
// | 模型安装类
// +----------------------------------------------------------------------
// | Copyright (c) 2015-2024 http://www.yicmf.com, All rights reserved.
// +----------------------------------------------------------------------
// | Author: 微尘 <yicmf@qq.com>
// +----------------------------------------------------------------------

namespace yicmf\tools;

use think\Config;

class Module
{
    //应用模块目录
    public $appPath = APP_PATH;
    //模板目录
//     public $templatePath;
    //静态资源目录
    public $extresPath = null;
    //错误信息
    public $error = null;
    //插件配置
    private $config = [];
    //当前模块名称
    private $module_name = null;
    //系统模块
    private $systemModuleList = ['Admin', 'Api', 'Install', 'Attachment', 'Libs'];

    /**
     * 构造方法.
     */
    public function __construct()
    {
        $this->extresPath = ROOT_PATH.'public/';
//         $this->templatePath = TEMPLATE_PATH . 'Default/';
    }

    /**
     * 连接.
     *
     * @return void
     */
    public static function getInstance()
    {
        return \Think\Think::instance('\\Libs\\System\\Module');
    }

    /**
     * 获取错误信息.
     *
     * @return string
     */
    public function getError()
    {
        return $this->error;
    }

    /**
     * 获取所有模块.
     *
     * @return Ambigous <unknown, multitype:, mixed, \Think\mixed, object>
     * @date		2015-8-4 上午7:44:41
     *
     * @author		@微尘 <yicmf@qq.com>
     *
     * @version	v1.0.0
     */
    public function getAll()
    {

        //取得模块目录名称
        $dirs = glob($this->appPath.'*');
        foreach ($dirs as $path) {
            if (is_dir($path)) {
                //目录名称
                $path = basename($path);
                //系统模块隐藏
                if (in_array($path, $this->systemModuleList)) {
                    continue;
                }
                $dirs_arr[] = $path;
            }
        }
        //取得已安装模块列表
        $moduleHasList = $this->getHasInstall();
        $moduleList = [];
        foreach ($moduleHasList as $keys=>$v) {
            //检查是否系统模块，如果是，直接不显示
            if (!$v['is_core'] && in_array($v['module'], $dirs_arr)) {//非核心切存在该模块
                $moduleList[$v['module']] = $v;
            }
//     		if ($v['is_core']) {
//     			$key = array_keys($dirs_arr, $v['module']);
//     			unset($dirs_arr[$key[0]]);
//     		}
        }

        return $moduleList;
//     	$module = S('module_all1');
//     	if ($module === false) {
//     		$dir = $this->getFile(APP_PATH);
//     		foreach ($dir as $subdir) {
//     			if (file_exists(APP_PATH . '/' . $subdir . '/Info/info.php') && $subdir != '.' && $subdir != '..') {
//     				$info = $this->getModule($subdir);
//     				$module[] = $info;

//     			}
//     		}

//     		S('module_all', $module);
//     	}

//     	return $module;
    }

    /**
     * 获取已经安装模块.
     *
     * @return Ambigous <\Think\mixed, boolean, string, NULL, multitype:, unknown, mixed, object>
     * @date		2015-8-5 下午8:51:26
     *
     * @author		@微尘 <yicmf@qq.com>
     *
     * @version	v1.0.0
     */
    public function getHasInstall()
    {
        return M('Module')->select();
    }

    /**检查模块是否已安装
     * @param $name
    * @auth 陈一枭
    */
    public function getModule($module)
    {
        $module_re = M('Module')->where(['module' => ucfirst($module)])->cache('common_module_'.strtolower($module))->find();
        if ($module_re === false || $module_re == null) {
            $m = $this->getInfo($module);
            if ($m != []) {
                if (intval($m['can_uninstall']) == 1) {
                    $m['is_setup'] = 0; //默认设为已安装，防止已安装的模块反复安装。
                } else {
                    $m['is_setup'] = 1;
                }
                $m['id'] = M('Module')->add($m);

                return $m;
            }
        } else {
            return $module_re;
        }
    }

    public function getModuleById($id)
    {
        $module = $this->where(['id' => $id])->find();
        if ($module === false || $module == null) {
            $m = $this->getInfo($module['name']);
            if ($m != []) {
                if ($m['can_uninstall']) {
                    $m['is_setup'] = 0; //默认设为已安装，防止已安装的模块反复安装。
                } else {
                    $m['is_setup'] = 1;
                }
                $m['id'] = $this->add($m);

                return $m;
            }
        } else {
            return $module;
        }
    }

    public function cleanModuleCache($name)
    {
        S('common_module_'.strtolower($name), null);
    }

    private function getInfo($name)
    {
        if (file_exists(APP_PATH.'/'.$name.'/Info/info.php')) {
            $module = require APP_PATH.'/'.$name.'/Info/info.php';

            return $module;
        } else {
            return [];
        }
    }

    /**
     * 获取文件列表.
     */
    private function getFile($folder)
    {
        //打开目录
        $fp = opendir($folder);
        //阅读目录
        while (false != $file = readdir($fp)) {
            //列出所有文件并去掉'.'和'..'
            if ($file != '.' && $file != '..') {
                //$file="$folder/$file";
                $file = "$file";

                //赋值给数组
                $arr_file[] = $file;
            }
        }
        //输出结果
        if (is_array($arr_file)) {
            while (list($key, $value) = each($arr_file)) {
                $files[] = $value;
            }
        }
        //关闭目录
        closedir($fp);

        return $files;
    }

    /**
     * 设置当前模块名称.
     *
     * @param type $name 模块名
     *
     * @return \Libs\System\Module
     */
    public function setName($name)
    {
        $this->module_name = $name;

        return $this;
    }

    /**
     * 获取模块基本配置信息.
     *
     * @param type $module_name 模块名(目录名)
     *
     * @return bool
     */
    public function config($module_name = '')
    {
        if (!empty($this->config) && empty($module_name)) {
            return $this->config;
        }
        if (empty($module_name)) {
            if ($this->module_name) {
                $module_name = $this->module_name;
            } else {
                $this->error = '模块名称不能为空！';

                return false;
            }
        }
        $module_name = ucwords($module_name);
        $config = [
            //模块目录
            'module' => $module_name,
            //模块名称
            'module_name' => $module_name,
            //图标地址，远程地址
            'icon' => '',
            //模块介绍地址
            'address' => '',
            //模块简介
            'introduce' => '',
            //模块作者
            'author' => '',
            //作者地址
            'authorsite' => '',
            //作者邮箱
            'authoremail' => '',
            //版本号，请不要带除数字外的其他字符
            'version' => '',
            //适配最低ShuipFCMS版本，
            'adaptation' => '',
            //签名
            'sign' => '',
            //依赖模块
            'depend' => [],
            //行为
            'tags' => [],
            //钩子
            'hook'=> [],
            //缓存
            'S' => [],
        ];
        //加载安装配置文件
        if (file_exists($this->appPath.$module_name.'/info.php')) {
            //加载
            try {
                $moduleConfig = include $this->appPath.$module_name.'/info.php';
                $config = array_merge($config, $moduleConfig);
            } catch (Exception $exc) {
            }
        }
        //检查是否安装，如果安装了，加载模块安装后的相关配置信息
        if ($this->isInstall($module_name)) {
            $moduleList = S('Module');
            $config = array_merge($moduleList[$module_name], $config);
        }
        $this->config = $config;

        return $config;
    }

    /**
     * 是否已经安装.
     *
     * @param type $module_name 模块名(目录名)
     *
     * @return bool
     */
    public function isInstall($module_name = '')
    {
        if (empty($module_name)) {
            if ($this->module_name) {
                $module_name = $this->module_name;
            } else {
                $this->error = '模块名称不能为空！';

                return false;
            }
        }
        $moduleList = S('Module');

        return (isset($moduleList[$module_name]) && $moduleList[$module_name]) ? true : false;
    }

    /**
     * 执行模块安装.
     *
     * @param type $module_name 模块名(目录名)
     *
     * @return bool
     */
    public function install($module_name = '')
    {
        defined('INSTALL') or define('INSTALL', true);
        if (empty($module_name)) {
            if ($this->module_name) {
                $module_name = $this->module_name;
            } else {
                $this->error = '请选择需要安装的模块！';

                return false;
            }
        }
        //已安装模块列表
        $moduleList = S('Module');
        //设置脚本最大执行时间
        set_time_limit(0);
        if ($this->competence($module_name) !== true) {
            return false;
        }

        //加载模块基本配置
        $config = $this->config($module_name);
        //版本检查
        if ($config['adaptation']) {
            if (version_compare(Config::get('yicmf.version'), $config['adaptation'], '>=') == false) {
                $this->error = '该模块要求系统最低版本为：'.$config['adaptation'].'！';

                return false;
            }
        }
        //依赖模块检测
        if (!empty($config['depend']) && is_array($config['depend'])) {
            foreach ($config['depend'] as $mod) {
                if ('Content' == $mod) {
                    continue;
                }
                if (!isset($moduleList[$mod])) {
                    $this->error = "安装该模块，需要安装依赖模块 {$mod} !";

                    return false;
                }
            }
        }
        //检查模块是否已经安装
        if ($this->isInstall($module_name)) {
            $this->error = '该模块已经安装，无法重复安装！';

            return false;
        }
        $model = D('Common/Module');
        if (!$model->create($config, 1)) {
            $this->error = $model->getError() ?: '安装初始化失败！';

            return false;
        }
        if ($model->add() == false) {
            $this->error = '安装失败！';

            return false;
        }
        //执行安装脚本
        if (!$this->runInstallScript($module_name)) {
            $this->installRollback($module_name);

            return false;
        }

        //执行数据库脚本安装
        $this->runSQL($module_name);
        //执行菜单项安装
        if ($this->installMenu($module_name) !== true) {
            $this->installRollback($module_name);

            return false;
        }
        //缓存注册
//         if (!empty($config['S'])) {
//             if (D('Common/Cache')->installModuleCache($config['S'], $config) !== true) {
//                 $this->error = D('Common/Cache')->getError();
//                 $this->installRollback($module_name);
//                 return false;
//             }
//         }
        $Dir = new \Org\Util\Dir();
        //前台模板
//         if (file_exists($this->appPath . "{$module_name}/Install/Template/")) {
//             //拷贝模板到前台模板目录中去
//             $Dir->copyDir($this->appPath . "{$module_name}/Install/Template/", $this->templatePath);
//         }
        //静态资源文件
        if (file_exists($this->appPath."{$module_name}/Install/Extres/")) {
            //拷贝模板到前台模板目录中去
            $Dir->copyDir($this->appPath."{$module_name}/Install/Extres/", $this->extresPath.ucwords($module_name).'/');
        }
        //安装行为  暂时取消
//         if (!empty($config['tags'])) {
//             D('Common/Behavior')->moduleBehaviorInstallation($module_name, $config['tags']);
//         }
        //安装钩子
       if (!empty($config['hook'])) {
           D('Admin/hook')->moduleInstallhook($module_name, $config['hook']);
       }
        //安装结束，最后调用安装脚本完成
        $this->runInstallScriptEnd($module_name);
        //更新缓存
        return true;
    }

    /**
     * 模块卸载.
     *
     * @param type $module_name 模块名(目录名)
     *
     * @return bool
     */
    public function uninstall($module_name = '')
    {
        defined('UNINSTALL') or define('UNINSTALL', true);
        if (empty($module_name)) {
            if ($this->module_name) {
                $module_name = $this->module_name;
            } else {
                $this->error = '请选择需要卸载的模块！';

                return false;
            }
        }
        //设置脚本最大执行时间
        set_time_limit(0);
        if ($this->competence($module_name) !== true) {
            return false;
        }
        $model = D('Common/Module');
        //取得该模块数据库中记录的安装信息
        $info = $model->where(['module' => $module_name])->find();
        if (empty($info)) {
            $this->error = '该模块未安装，无需卸载！';

            return false;
        }
        if ($info['is_core']) {
            $this->error = '内置模块，不能卸载！';

            return false;
        }
        //删除
        if ($model->where(['module' => $module_name])->delete() == false) {
            $this->error = '卸载失败！';

            return false;
        }
        //删除权限
        M('Access')->where(['app' => $module_name])->delete();
        //移除菜单项和权限项
        M('Menu')->where(['app' => $module_name])->delete();
        //去除对应行为规则
        D('Common/Behavior')->moduleBehaviorUninstall($module_name);
        //注销缓存
        D('Common/Cache')->deleteCacheModule($module_name);
        //删除菜单项
        $this->uninstallMenu($module_name);
        //执行卸载脚本
        if (!$this->runInstallScript($module_name, 'Uninstall')) {
            $this->installRollback($module_name);

            return false;
        }
        //执行数据库脚本安装
        $this->runSQL($module_name, 'Uninstall');
        $Dir = new \Dir();
        //删除模块前台模板
//         $Dir->delDir($this->templatePath . $module_name . DIRECTORY_SEPARATOR);
        //静态资源移除
        $Dir->delDir($this->extresPath.strtolower($module_name).DIRECTORY_SEPARATOR);
        //卸载结束，最后调用卸载脚本完成
        $this->runInstallScriptEnd($module_name, 'Uninstall');
        //更新缓存
//         S('Module', NULL);
        //移除目录
        $Dir->delDir(APP_PATH.$module_name.DIRECTORY_SEPARATOR);

        return true;
    }

    /**
     * 模块升级.
     *
     * @param type $module_name 模块名(目录名)
     *
     * @return bool
     */
    public function upgrade($module_name = '')
    {
        if (empty($module_name)) {
            if ($this->module_name) {
                $module_name = $this->module_name;
            } else {
                $this->error = '请选择需要升级的模块！';

                return false;
            }
        }
        //设置脚本最大执行时间
        set_time_limit(0);
        if ($this->competence($module_name) !== true) {
            return false;
        }
        $model = D('Common/Module');
        //取得该模块数据库中记录的安装信息
        $info = $model->where(['module' => $module_name])->find();
        if (empty($info)) {
            $this->error = '该模块未安装，无需升级！';

            return false;
        }
        //执行数据库升级脚本
        $this->runSQL($module_name, 'Upgrade');
        //执行卸载脚本
        if (!$this->runInstallScript($module_name, 'Upgrade')) {
            $this->installRollback($module_name);

            return false;
        }
        //加载配置
        $config = $this->config($module_name);
        if (!empty($config)) {
            //更新版本号
            $model->where(['module' => $module_name])->save(['version' => $config['version'], 'updatetime' => time()]);
        }
        //卸载结束，最后调用卸载脚本完成
        $this->runInstallScriptEnd($module_name, 'Upgrade');
        //更新缓存
        S('Module', null);

        return true;
    }

    /**
     * 目录权限检查.
     *
     * @param type $module_name 模块名称
     *
     * @return bool
     */
    public function competence($module_name = '')
    {
        //模板目录权限检测
//         if ($this->chechmod($this->templatePath) == false) {
//             $this->error = '目录 ' . $this->templatePath . ' 没有可写权限！';
//             return false;
//         }
        if ($module_name && file_exists($this->extresPath.$module_name)) {
            if ($this->chechmod($this->extresPath.$module_name) == false) {
                $this->error = '目录 '.$this->extresPath.$module_name.' 没有21可写权限！';

                return false;
            }
        }

        //静态资源目录权限检测
        if (!file_exists($this->extresPath)) {
            //创建目录
            if (mkdir($this->extresPath, 0777, true) == false) {
                $this->error = '目录 '.$this->extresPath.' 创建失败，请检查是否有可写权限！';

                return false;
            }
        }

        //权限检测
        if ($this->chechmod($this->extresPath) == false) {
            $this->error = '目录 '.$this->extresPath.' 没有可1写权限！';

            return false;
        }

        return true;
    }

    /**
     * 检查对应目录是否有相应的权限.
     *
     * @param type $path 目录地址
     *
     * @return bool
     */
    protected function chechmod($path)
    {
        //检查模板文件夹是否有可写权限 TEMPLATE_PATH
        $tfile = '_test.txt';
        $fp = @fopen($path.$tfile, 'w');
        if (!$fp) {
            return false;
        }
        fclose($fp);
        $rs = @unlink($path.$tfile);
        if (!$rs) {
            return false;
        }

        return true;
    }

    /**
     * 卸载菜单项项.
     *
     * @param type $module_name
     *
     * @return bool
     */
    private function uninstallMenu($module_name = '')
    {
        if (empty($module_name)) {
            if ($this->module_name) {
                $module_name = $this->module_name;
            } else {
                $this->error = '模块名称不能为空！';

                return false;
            }
        }
        M('Menu')->where(['module' => $module_name])->delete();

        return true;
    }

    /**
     * 安装菜单项.
     *
     * @param type $module_name 模块名称
     * @param type $file        文件
     *
     * @return bool
     */
    private function installMenu($module_name = '', $file = 'Menu')
    {
        if (empty($module_name)) {
            if ($this->module_name) {
                $module_name = $this->module_name;
            } else {
                $this->error = '模块名称不能为空！';

                return false;
            }
        }
        $path = $this->appPath."{$module_name}/Install/{$file}.php";
        //检查是否有安装脚本
        if (!file_exists($path)) {
            return true;
        }
        $menu = include $path;
        if (empty($menu)) {
            return true;
        }
        $status = D('Admin/Menu')->installModuleMenu($menu, $this->config($module_name));
        if ($status !== false) {
            return true;
        } else {
            $this->error = D('Admin/Menu')->getError() ?: '安装菜单项出现错误！';

            return false;
        }
    }

    /**
     * 执行安装脚本.
     *
     * @param type $module_name 模块名(目录名)
     *
     * @return bool
     */
    private function runInstallScript($module_name = '', $Dir = 'Install')
    {
        if (empty($module_name)) {
            if ($this->module_name) {
                $module_name = $this->module_name;
            } else {
                $this->error = '模块名称不能为空！';

                return false;
            }
        }
        //检查是否有安装脚本
        if (require_cache($this->appPath."{$module_name}/{$Dir}/{$Dir}.class.php") !== true) {
            return true;
        }
        $className = "\\{$module_name}\\{$Dir}\\{$Dir}";
        $installObj = \Think\Think::instance($className);
        //执行安装
        if (false == $installObj->run()) {
            $this->error = $installObj->getError();

            return false;
        }

        return true;
    }

    /**
     * 执行安装脚本.
     *
     * @param type $module_name 模块名(目录名)
     *
     * @return bool
     */
    private function runInstallScriptEnd($module_name = '', $Dir = 'Install')
    {
        if (empty($module_name)) {
            if ($this->module_name) {
                $module_name = $this->module_name;
            } else {
                $this->error = '模块名称不能为空！';

                return false;
            }
        }
        //检查是否有安装脚本
        if (require_cache($this->appPath."{$module_name}/{$Dir}/{$Dir}.class.php") !== true) {
            return true;
        }
        $className = "\\{$module_name}\\{$Dir}\\{$Dir}";
        $installObj = \Think\Think::instance($className);
        //执行安装
        if (false == $installObj->end()) {
            $this->error = $installObj->getError();

            return false;
        }

        return true;
    }

    /**
     * 执行安装数据库脚本.
     *
     * @param type $module_name 模块名(目录名)
     *
     * @return bool
     */
    private function runSQL($module_name = '', $Dir = 'Install')
    {
        if (empty($module_name)) {
            if ($this->module_name) {
                $module_name = $this->module_name;
            } else {
                $this->error = '模块名称不能为空！';

                return false;
            }
        }
        $path = $this->appPath."{$module_name}/{$Dir}/{$module_name}.sql";
        if (!file_exists($path)) {
            return true;
        }
        $sql = file_get_contents($path);
        $sql = $this->resolveSQL($sql, Config::get('database.prefix'));
        if (!empty($sql) && is_array($sql)) {
            foreach ($sql as $sql_split) {
                M()->execute($sql_split);
            }
        }

        return true;
    }

    /**
     * 安装回滚.
     *
     * @param type $module_name 模块名(目录名)
     */
    private function installRollback($module_name = '')
    {
        if (empty($module_name)) {
            if ($this->module_name) {
                $module_name = $this->module_name;
            } else {
                $this->error = '模块名称不能为空！';

                return false;
            }
        }
        //删除安装状态
        M('Module')->where(['module' => $module_name])->delete();
        //更新缓存
        S('Module', null);
    }

    /**
     * 分析处理sql语句，执行替换前缀都功能。
     *
     * @param string $sql      原始的sql
     * @param string $tablepre 表前缀
     */
    private function resolveSQL($sql, $tablepre)
    {
        if ($tablepre != 'shuipfcms_') {
            $sql = str_replace('shuipfcms_', $tablepre, $sql);
        }
        $sql = preg_replace('/TYPE=(InnoDB|MyISAM|MEMORY)( DEFAULT CHARSET=[^; ]+)?/', 'ENGINE=\\1 DEFAULT CHARSET=utf8', $sql);
        if ($r_tablepre != $s_tablepre) {
            $sql = str_replace($s_tablepre, $r_tablepre, $sql);
        }
        $sql = str_replace("\r", "\n", $sql);
        $ret = [];
        $num = 0;
        $queriesarray = explode(";\n", trim($sql));
        unset($sql);
        foreach ($queriesarray as $query) {
            $ret[$num] = '';
            $queries = explode("\n", trim($query));
            $queries = array_filter($queries);
            foreach ($queries as $query) {
                $str1 = substr($query, 0, 1);
                if ($str1 != '#' && $str1 != '-') {
                    $ret[$num] .= $query;
                }
            }
            $num++;
        }

        return $ret;
    }
}
